"""
緩存管理器 - 實現LRU和FIFO替換策略
"""

import os
import sys
import time
from abc import ABC, abstractmethod
from collections import OrderedDict, deque
from typing import Dict, Optional, Tuple, List

# 添加当前目录到Python路径，支持直接运行
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
    sys.path.insert(0, current_dir)

from page_manager import Page, PageManager
from constants import *
from statistics import CacheStats, Logger
from page_lock_manager import LockManager


class CacheManager(ABC):
    """缓存管理器抽象基类"""
    
    def __init__(self, page_manager: PageManager, cache_size: int = DEFAULT_CACHE_SIZE, enable_locking: bool = True):
        self.page_manager = page_manager
        self.cache_size = max(MIN_CACHE_SIZE, min(cache_size, MAX_CACHE_SIZE))
        self.cache: Dict[int, Page] = {}  # 缓存页字典
        self.stats = CacheStats()
        self.stats.max_cache_size = self.cache_size
        self.logger = Logger()
        self.enable_locking = enable_locking
        
        # 锁管理器（如果页管理器已经有锁管理器，使用同一个）
        if self.enable_locking:
            if hasattr(page_manager, 'lock_manager') and page_manager.lock_manager:
                self.lock_manager = page_manager.lock_manager
            else:
                self.lock_manager = LockManager()
        else:
            self.lock_manager = None
        
        self.logger.info("INIT", 0, f"Cache initialized with size {self.cache_size}, locking {'enabled' if enable_locking else 'disabled'}")
    
    @abstractmethod
    def _update_access_order(self, page_id: int):
        """更新页面访问顺序（由子类实现）"""
        pass
    
    @abstractmethod
    def _select_victim_page(self) -> int:
        """选择要替换的页面（由子类实现）"""
        pass
    
    def get_page(self, page_id: int) -> bytes:
        """获取页数据（带缓存），支持预读优化"""
        # 檢查頁是否在緩存中
        if page_id in self.cache:
            # 缓存命中
            self.stats.record_hit()
            self._update_access_order(page_id)
            page = self.cache[page_id]
            self.logger.info("CACHE_HIT", page_id, "")
            
            # 通知页面管理器这是缓存命中（用于预读统计）
            if hasattr(self.page_manager, 'readahead_engine') and self.page_manager.readahead_engine:
                self.page_manager.readahead_engine.record_access(page_id, from_cache=True)
            
            return page.data
        
        # 缓存失效，需要从磁盘读取
        self.stats.record_miss()
        self.logger.info("CACHE_MISS", page_id, "")
        
        # 从磁盘读取页数据（支持预读）
        try:
            page_data = self.page_manager.read_page(page_id, from_cache=False)
            page = Page(page_id, page_data)
        except Exception as e:
            self.logger.error("DISK_READ", page_id, f"Failed: {e}")
            raise
        
        # 检查是否需要替换页面
        if len(self.cache) >= self.cache_size:
            victim_page_id = self._select_victim_page()
            self._evict_page(victim_page_id)
        
        # 将页面加入缓存
        self.cache[page_id] = page
        self.stats.cache_size = len(self.cache)
        self._update_access_order(page_id)
        
        self.logger.info("CACHE_LOAD", page_id, f"Loaded into cache")
        return page_data
    
    def put_page(self, page_id: int, data: bytes) -> bool:
        """将页数据写入缓存并标记为脏页"""
        # 創建或更新緩存中的頁
        if page_id in self.cache:
            page = self.cache[page_id]
            page.data = data[:PAGE_SIZE] if len(data) > PAGE_SIZE else data + bytes(PAGE_SIZE - len(data))
            page.is_dirty = True
            self._update_access_order(page_id)
            self.logger.info("CACHE_UPDATE", page_id, "Updated in cache")
        else:
            # 检查是否需要替换页面
            if len(self.cache) >= self.cache_size:
                victim_page_id = self._select_victim_page()
                self._evict_page(victim_page_id)
            
            # 创建新页并加入缓存
            page = Page(page_id, data)
            page.is_dirty = True
            self.cache[page_id] = page
            self.stats.cache_size = len(self.cache)
            self._update_access_order(page_id)
            self.logger.info("CACHE_PUT", page_id, "Added to cache")
        
        return True
    
    def flush_page(self, page_id: int) -> bool:
        """刷新指定页到磁盘"""
        if page_id not in self.cache:
            self.logger.warn("FLUSH", page_id, "Page not in cache")
            return False
        
        page = self.cache[page_id]
        if not page.is_dirty:
            self.logger.info("FLUSH", page_id, "Page not dirty, skip flush")
            return True
        
        try:
            success = self.page_manager.write_page(page_id, page.data)
            if success:
                page.is_dirty = False
                self.logger.info("FLUSH", page_id, "Flushed to disk")
            else:
                self.logger.error("FLUSH", page_id, "Failed to flush")
            return success
        except Exception as e:
            self.logger.error("FLUSH", page_id, f"Exception during flush: {e}")
            return False
    
    def flush_all_pages(self) -> bool:
        """刷新所有脏页到磁盘"""
        success_count = 0
        total_dirty = 0
        
        for page_id, page in self.cache.items():
            if page.is_dirty:
                total_dirty += 1
                if self.flush_page(page_id):
                    success_count += 1
        
        self.logger.info("FLUSH_ALL", 0, f"Flushed {success_count}/{total_dirty} dirty pages")
        return success_count == total_dirty
    
    def _evict_page(self, page_id: int):
        """驱逐页面"""
        if page_id not in self.cache:
            return
        
        page = self.cache[page_id]
        
        # 如果是脏页，先刷新到磁盘
        if page.is_dirty:
            self.flush_page(page_id)
        
        # 从缓存中移除
        del self.cache[page_id]
        self.stats.cache_size = len(self.cache)
        self.stats.record_replacement()
        self.logger.info("EVICT", page_id, "Page evicted from cache")
    
    def invalidate_page(self, page_id: int):
        """使页面无效（从缓存中移除但不刷新）"""
        if page_id in self.cache:
            del self.cache[page_id]
            self.stats.cache_size = len(self.cache)
            self.logger.info("INVALIDATE", page_id, "Page invalidated")
    
    def get_cached_pages(self) -> List[int]:
        """获取缓存中的页面ID列表"""
        return list(self.cache.keys())
    
    def get_dirty_pages(self) -> List[int]:
        """获取缓存中的脏页ID列表"""
        return [page_id for page_id, page in self.cache.items() if page.is_dirty]
    
    def get_stats(self) -> CacheStats:
        """获取缓存统计信息"""
        self.stats.cache_size = len(self.cache)
        return self.stats
    
    def clear_cache(self):
        """清空缓存（先刷新所有脏页）"""
        self.flush_all_pages()
        self.cache.clear()
        self.stats.cache_size = 0
        self.logger.info("CLEAR", 0, "Cache cleared")
    
    def is_page_cached(self, page_id: int) -> bool:
        """检查页面是否在缓存中（用于预读决策）"""
        return page_id in self.cache
    
    def prefetch_page(self, page_id: int) -> bool:
        """预读页面到缓存（如果不在缓存中且有空间）"""
        # 如果页面已在缓存中，无需预读
        if page_id in self.cache:
            return True
        
        # 如果缓存空间不足，不进行预读（避免影响正常访问）
        if len(self.cache) >= self.cache_size * 0.9:  # 预留10%空间
            return False
        
        try:
            # 静默读取页面（不触发额外预读）
            page_data = self.page_manager._read_page_from_disk(page_id)
            page = Page(page_id, page_data)
            
            # 添加到缓存
            self.cache[page_id] = page
            self.stats.cache_size = len(self.cache)
            self._update_access_order(page_id)
            
            self.logger.info("PREFETCH", page_id, "Page prefetched successfully")
            return True
            
        except Exception as e:
            self.logger.info("PREFETCH", page_id, f"Prefetch failed: {e}")
            return False
    
    def get_readahead_statistics(self) -> dict:
        """获取预读相关统计信息"""
        base_stats = {
            'cache_size': len(self.cache),
            'cache_utilization': len(self.cache) / self.cache_size if self.cache_size > 0 else 0,
            'hit_rate': self.stats.hit_rate
        }
        
        # 如果页面管理器有预读统计，则包含
        if hasattr(self.page_manager, 'get_readahead_statistics'):
            readahead_stats = self.page_manager.get_readahead_statistics()
            base_stats.update(readahead_stats)
        
        return base_stats
    
    def print_cache_status(self):
        """打印缓存状态"""
        print(f"\n=== 缓存管理器状态 ===")
        print(f"缓存策略: {self.__class__.__name__}")
        print(f"缓存大小: {len(self.cache)}/{self.cache_size}")
        print(f"缓存页面: {sorted(self.cache.keys())}")
        print(f"脏页面: {self.get_dirty_pages()}")
        print(f"统计信息: {self.stats}")
        print("=" * 35)


class LRUCache(CacheManager):
    """LRU（最近最少使用）缓存管理器"""
    
    def __init__(self, page_manager: PageManager, cache_size: int = DEFAULT_CACHE_SIZE, enable_locking: bool = True):
        super().__init__(page_manager, cache_size, enable_locking)
        self.access_order: OrderedDict[int, float] = OrderedDict()  # 页面访问顺序
        self.logger.info("INIT", 0, "LRU cache manager initialized")
    
    def _update_access_order(self, page_id: int):
        """更新页面访问顺序"""
        current_time = time.time()
        if page_id in self.access_order:
            # 移动到最后（最近使用）
            del self.access_order[page_id]
        self.access_order[page_id] = current_time
    
    def _select_victim_page(self) -> int:
        """选择最近最少使用的页面进行替换"""
        if not self.access_order:
            raise RuntimeError("No pages to evict")
        
        # OrderedDict的第一个元素是最久未使用的
        lru_page_id = next(iter(self.access_order))
        del self.access_order[lru_page_id]
        return lru_page_id
    
    def invalidate_page(self, page_id: int):
        """使页面无效"""
        super().invalidate_page(page_id)
        if page_id in self.access_order:
            del self.access_order[page_id]
    
    def clear_cache(self):
        """清空缓存"""
        super().clear_cache()
        self.access_order.clear()


class FIFOCache(CacheManager):
    """FIFO（先進先出）缓存管理器"""
    
    def __init__(self, page_manager: PageManager, cache_size: int = DEFAULT_CACHE_SIZE, enable_locking: bool = True):
        super().__init__(page_manager, cache_size, enable_locking)
        self.page_queue: deque = deque()  # 页面进入顺序队列
        self.logger.info("INIT", 0, "FIFO cache manager initialized")
    
    def _update_access_order(self, page_id: int):
        """更新页面访问顺序（FIFO只在首次加载时记录）"""
        if page_id not in self.page_queue:
            self.page_queue.append(page_id)
    
    def _select_victim_page(self) -> int:
        """选择最先进入的页面进行替换"""
        if not self.page_queue:
            raise RuntimeError("No pages to evict")
        
        # 取出最先进入的页面
        return self.page_queue.popleft()
    
    def invalidate_page(self, page_id: int):
        """使页面无效"""
        super().invalidate_page(page_id)
        if page_id in self.page_queue:
            self.page_queue.remove(page_id)
    
    def clear_cache(self):
        """清空缓存"""
        super().clear_cache()
        self.page_queue.clear()


class ClockCache(CacheManager):
    """Clock（时钟）缓存管理器 - 近似LRU的简单实现"""
    
    def __init__(self, page_manager: PageManager, cache_size: int = DEFAULT_CACHE_SIZE, enable_locking: bool = True):
        super().__init__(page_manager, cache_size, enable_locking)
        self.page_list: List[int] = []  # 页面列表
        self.reference_bits: Dict[int, bool] = {}  # 引用位
        self.clock_hand = 0  # 时钟指针
        self.logger.info("INIT", 0, "Clock cache manager initialized")
    
    def _update_access_order(self, page_id: int):
        """更新页面访问顺序"""
        if page_id not in self.page_list:
            self.page_list.append(page_id)
        self.reference_bits[page_id] = True  # 设置引用位
    
    def _select_victim_page(self) -> int:
        """使用时钟算法选择要替换的页面"""
        if not self.page_list:
            raise RuntimeError("No pages to evict")
        
        # 时钟算法：寻找引用位为0的页面
        while True:
            page_id = self.page_list[self.clock_hand]
            
            if not self.reference_bits.get(page_id, False):
                # 找到引用位为0的页面，移除并返回
                self.page_list.pop(self.clock_hand)
                if page_id in self.reference_bits:
                    del self.reference_bits[page_id]
                
                # 调整时钟指针
                if self.clock_hand >= len(self.page_list) and self.page_list:
                    self.clock_hand = 0
                
                return page_id
            else:
                # 清除引用位，移动时钟指针
                self.reference_bits[page_id] = False
                self.clock_hand = (self.clock_hand + 1) % len(self.page_list)
    
    def invalidate_page(self, page_id: int):
        """使页面无效"""
        super().invalidate_page(page_id)
        if page_id in self.page_list:
            idx = self.page_list.index(page_id)
            self.page_list.remove(page_id)
            if idx <= self.clock_hand and self.clock_hand > 0:
                self.clock_hand -= 1
        if page_id in self.reference_bits:
            del self.reference_bits[page_id]
    
    def clear_cache(self):
        """清空缓存"""
        super().clear_cache()
        self.page_list.clear()
        self.reference_bits.clear()
        self.clock_hand = 0
